package com.xhj.mybatis.plugin;

import com.xhj.mybatis.util.MapUtil;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * Plugin
 *
 * @author XJks
 * @description
 */
public class Plugin implements InvocationHandler {

    /**
     * 被代理的目标对象
     */
    private final Object target;

    /**
     * 拦截器，用于处理方法调用
     */
    private final Interceptor interceptor;

    /**
     * 存储需要拦截的方法签名映射
     */
    private final Map<Class<?>, Set<Method>> signatureMap;

    /**
     * 构造方法，初始化目标对象、拦截器和方法签名映射
     *
     * @param target       目标对象
     * @param interceptor  拦截器
     * @param signatureMap 方法签名映射
     */
    private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
        this.target = target;
        this.interceptor = interceptor;
        this.signatureMap = signatureMap;
    }

    /**
     * 包装目标对象，如果需要拦截则返回代理对象，否则返回原对象
     *
     * @param target      目标对象
     * @param interceptor 拦截器
     * @return 代理对象或原对象
     */
    public static Object wrap(Object target, Interceptor interceptor) {
        // 获取拦截器上定义的需要拦截的方法签名
        Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
        // 获取目标对象的类型
        Class<?> type = target.getClass();
        // 获取所有需要拦截的接口
        Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
        // 如果目标对象实现了需要拦截的接口，则创建代理对象
        if (interfaces.length > 0) {
            return Proxy.newProxyInstance(
                    type.getClassLoader(), // 使用目标对象的类加载器
                    interfaces,            // 代理的接口
                    new Plugin(target, interceptor, signatureMap)); // 代理处理器
        }
        // 否则直接返回原对象
        return target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            // 获取当前方法所属类对应的拦截方法集合
            Set<Method> methods = signatureMap.get(method.getDeclaringClass());
            // 如果该方法需要被拦截，则调用拦截器
            if (methods != null && methods.contains(method)) {
                return interceptor.intercept(new Invocation(target, method, args));
            }
            // 否则直接调用目标对象的方法
            return method.invoke(target, args);
        } catch (Exception e) {
            // 抛出异常
            throw new RuntimeException(e);
        }
    }

    /**
     * 解析拦截器上的@Intercepts注解，获取需要拦截的方法签名映射
     *
     * @param interceptor 拦截器
     * @return 方法签名映射
     */
    private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
        // 获取@Intercepts注解
        Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
        // 如果没有@Intercepts注解，抛出异常
        if (interceptsAnnotation == null) {
            throw new RuntimeException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
        }
        // 获取所有方法签名
        Signature[] sigs = interceptsAnnotation.value();
        // 创建方法签名映射
        Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
        // 遍历每个签名，加入到映射中
        for (Signature sig : sigs) {
            // 通过签名的类型获取对应的方法集合
            Set<Method> methods = MapUtil.computeIfAbsent(signatureMap, sig.type(), k -> new HashSet<>());
            try {
                // 通过反射获取方法对象
                Method method = sig.type().getMethod(sig.method(), sig.args());
                // 将方法加入到集合中
                methods.add(method);
            } catch (NoSuchMethodException e) {
                // 如果方法不存在，抛出异常
                throw new RuntimeException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
            }
        }
        return signatureMap;
    }

    /**
     * 获取目标类及其父类实现的所有需要拦截的接口
     *
     * @param type         目标类
     * @param signatureMap 方法签名映射
     * @return 需要拦截的接口数组
     */
    private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
        // 创建接口集合，用于存储需要拦截的接口
        Set<Class<?>> interfaces = new HashSet<>();
        // 遍历类的继承层次结构
        while (type != null) {
            for (Class<?> c : type.getInterfaces()) {
                // 只添加需要拦截的接口
                if (signatureMap.containsKey(c)) {
                    interfaces.add(c);
                }
            }
            // 获取父类，继续遍历
            type = type.getSuperclass();
        }
        // 转换为数组返回
        return interfaces.toArray(new Class<?>[0]);
    }

}
